/**
 * TODO: refreshToken
 * [客户端](https://blog.csdn.net/qq_33270001/article/details/89473843)
 * [Oauth2.0 引入了 refresh token](https://blog.csdn.net/weixin_41282397/article/details/81908025)
 * [response of MI](https://dev.mi.com/docs/passport/refresh-token/)
 * [spotify-web-api-node](https://ask.csdn.net/questions/2022431)
 */
const createError = require('http-errors');
const jwt = require('jsonwebtoken');
const svgCaptcha = require('svg-captcha');
const { model } = require('mongoose');
const pwdReg = require('../utils/pwd-reg');
const only = require('../utils/only');

// 响应体规则: https://lionoggo.blog.csdn.net/article/details/52689010
const Account = model('accounts');
const captchaConfig = {
  size: 4, // 验证码长度
  ignoreChars: 'O01liI', // 验证码字符中排除O01i
  noise: 3, // 干扰线条数量
  height: 44,
  background: '#cc9966',
};
const oQueryFilter = {
  __v: 0,
  created_at: 0,
  update_at: 0,
  destroy_at: 0,
  attempts: 0,
};
// [nodeJs v6.0之后官方不建议使用Buffer()构造函数](https://blog.csdn.net/weixin_41058481/article/details/105041572)
// const cert = Buffer.from(process.env.TOKEN_PRIVATE_KEY, 'base64');

/**
 * @title 校验密码 -> 在Account.plugin的passwordValidator中实现,此处不再需要
 */
const pwdRegHandler = async (req, res, next) => {
  try {
    const { password } = req.body;
    if (password && pwdReg(password)) {
      next();
      return;
    }
    next(createError({
      name: 'ValidationError',
      message: 'Validation error: Validation is on password failed',
      errors: [],
    }));
  } catch (error) {
    next(error);
  }
};

/**
 * @title 获取验证码
 */
const getCaptchaHandler = async (req, res, next) => {
  try {
    const captcha = svgCaptcha.create(captchaConfig);
    const {
      // text,
      data,
    } = captcha;
    // console.log('line 58: ', text);
    req.session.captcha = captcha.text.toLowerCase(); // 存session用于验证接口获取文字码
    req.session.save();
    res.type('svg').status(200).send(data);
    // res.status(200).json({
    //   code: 0,
    //   msg: '操作成功',
    //   data: {
    //     svgCaptcha: data,
    //   },
    // });
  } catch (error) {
    next(error);
  }
};

/**
 * @title 注册 middleware
 */
const registerHandler = async (req, res, next) => {
  try {
    const { email, password } = req.body;

    // TODO: 当前被注销的username还是可以重新注册(是否添加过渡期???)

    // const account = await Account.register(new Account({ email }), password);
    const oAccount = new Account({ email, password });// 生成_id和唯一username
    await oAccount.setPassword(password);// 生成salt和hash
    await oAccount.save();// 保存至数据库
    const { error, user } = await Account.authenticate()(email, password);// 验证密码
    if (!user) {
      next(error);
      return;
    }
    res.json({
      code: 0,
      msg: '注册成功',
      data: {
        email: user.email,
      },
    });
  } catch (error) {
    next(error);
  }
};

/**
 * @title 验证密码 authenticate('local') middleware || 登录 (create token) middleware
 */
const loginHandler = async (req, res, next) => {
  try {
    const { email, password, captcha } = req.body;
    // 1. 验证 captcha
    if (!captcha || captcha.toLowerCase() !== req.session.captcha) {
      res.status(200).json({
        code: -1,
        msg: 'Invalid captcha',
        data: null,
      });
      return;
    }
    // await req.session.destroy();// 销毁后save都不能调用
    /**
     * 2. user is Instance
     */
    const { error, user } = await Account.authenticate('local')(email, password);
    if (!user) {
      next(error);
      return;
    }
    if (user.destroy_at) {
      res.status(200).json({
        code: -1,
        msg: '用户已注销',
        data: {
          destroy_at: user.destroy_at,
        },
      });
      return;
    }
    /**
     * ---------------------------------
     * 3. 登录 (create token) middleware
     * ---------------------------------
     */
    const { _id: userId, last } = user;
    const sEncoded = await jwt.sign(
      {
        id: userId,
        last,
      },
      process.env.TOKEN_PRIVATE_KEY,
      {
        // algorithm: 'RS256',
        // expiresIn：以 {Number} 秒 或 {String} 時間跨度zeit/ms的字符串表示
        expiresIn: process.env.TOKEN_EXPIRED, // [3600, '3600000', '1h']
      },
    );
    // req.session.token = sEncoded;

    // 4. 登录成功后清除captcha
    if (req.session.captcha) delete req.session.captcha;
    req.session.isLogin = userId;
    await req.session.save();

    // 5. 重置失敗嘗試次數为0
    await user.resetAttempts();

    res.status(200).json({
      code: 0,
      msg: '登录成功',
      data: { token: sEncoded },
    });
  } catch (error) {
    next(error);
  }
};

/**
 * @title 校验token
 */
const verifyTokenHandler = async (req, res, next) => {
  try {
    if (!req.session.isLogin && process.env.NODE_ENV === 'production') {
      res.status(200).json({
        code: -1,
        msg: '用户已注销，请重新登录！',
        data: null,
      });
      return;
    }
    const sAuthorization = req.headers.authorization;
    if ((typeof sAuthorization === 'string') && (sAuthorization.constructor === String)) {
      const sToken = sAuthorization.split(' ')[1];
      if (sToken) {
        // https://jwt.io/
        // eslint-disable-next-line
        const oDecoded = await jwt.verify(
          sToken,
          process.env.TOKEN_PRIVATE_KEY,
          {
            // algorithm: ['RS256'],
          },
        );
        /**
         * id: string
         * last: string 数据库存储的最后登录成功时间
         * iat: 1614569775, 生成时间(s)
         * exp: 1614573375, 过期时间(s)
         *  unit:(s) -> 需转化为ms后,在通过`new Date()`得到时间
         */
        const oFindUser = await Account.findById(oDecoded.id, oQueryFilter);
        if (!oFindUser) { // 为null的情况下
          next(createError({
            name: 'JsonWebTokenError',
            message: 'user does not exist',
            errors: [],
          }));
          return;
        }
        res.locals.user = oFindUser;
        next();
        return;
      }
      next(createError({
        name: 'JsonWebTokenError',
        message: 'jwt must be provided: `Bearer xxx`',
        errors: [],
      }));
      return;
    }
    next(createError({
      name: 'JsonWebTokenError',
      message: 'Authorization must be string',
      errors: [],
    }));
  } catch (error) {
    next(error);
  }
};

/**
 * @title 登出
 */
const logoutHandler = async (req, res, next) => {
  try {
    // req.logout();// passport注入的方法,未知使用
    if (req.session.isLogin) delete req.session.isLogin;
    await req.session.save();
    res.json({
      code: 0,
      msg: '登出成功',
      data: null,
    });
  } catch (error) {
    next(error);
  }
};

/**
 * @title 更新个人信息
 */
const updateHandler = async (req, res, next) => {
  try {
    const {
      oldPwd,
      newPwd,
      nickname,
      phone,
    } = req.body;
    const { _id: userId } = res.locals.user;
    const oAccount = await Account.findById(userId, oQueryFilter);

    if (oldPwd && newPwd) await oAccount.changePassword(oldPwd, newPwd);

    // `tank.size = 'large'`; 用 `tank.set({ size: 'large' })`实现。
    if (nickname) oAccount.nickname = nickname;
    if (phone) oAccount.set({ phone });
    const oUpdate = await oAccount.save();

    // Document转化为Object: oUpdate.toJSON() or oUpdate.toObject()
    const user = only(oUpdate.toObject(), ['hash', 'salt']);
    res.status(200).json({
      code: 0,
      msg: '用户更新成功',
      data: { user },
    });
  } catch (error) {
    next(error);
  }
};

/**
 * @title 注销账户
 */
const destroyHandler = async (req, res, next) => {
  try {
    const { _id: userId } = res.locals.user;
    const oAccount = await Account.findByIdAndUpdate(
      userId + 1,
      { $set: { destroy_at: new Date() } },
      {
        new: true, // 返回修改后的新值
      },
    );
    if (oAccount.destroy_at) {
      res.status(200).json({
        code: 0,
        msg: '注销成功',
        data: null,
      });
      return;
    }
    next(createError({
      name: 'MongoError',
      message: '注销失败',
      errors: [],
    }));
  } catch (error) {
    next(error);
  }
};

/**
 * @title 获取个人信息
 */
const infoHandler = async (req, res, next) => {
  try {
    const oUser = res.locals.user;
    // delete res.locals;
    res.status(200).json({
      code: 0,
      msg: '操作成功',
      data: {
        user: oUser,
      },
    });
  } catch (error) {
    next(error);
  }
};

module.exports = {
  pwdRegHandler,
  getCaptchaHandler,

  registerHandler,
  loginHandler,

  verifyTokenHandler,

  logoutHandler,
  updateHandler,
  destroyHandler,
  infoHandler,
};
